package com.example.springboot3.result;

import com.example.springboot3.config.VelocityRenderer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static com.example.springboot3.result.Test.TESTUtils.*;


public class Test {
    protected static class Constants {

        public static class Limit {
            public static final int POSITIVE_LIMIT = 2;
            public static final int NEGATIVE_LIMIT = 1;
            public static final int CHILD_POSITIVE_LIMIT = 1;
            public static final int CHILD_NEGATIVE_LIMIT = 1;
            public static final int TOP_N = 5;
        }

        public static class Sorting {
            public static final String SORT_KEY = "contribution";
        }

        public static class Algorithm {
            public static final String ADDITION = "addition";
            public static final String DIVISION = "division";
        }

        public static class Trend {
            public static final String UP = "up";
            public static final String DOWN = "down";
            public static final String FLAT = "flat";
        }
    }

    private static final ObjectMapper mapper = new ObjectMapper();
    private static final Map<String, Function<JsonNode, Object>> FIELD_EXTRACTORS =
            ImmutableMap.<String, Function<JsonNode, Object>>builder()
                    .put("typeName", node -> safeGetText(node, "typeName", ""))
                    .put("base_value", node -> safeGetDouble(node, "base_value", 0.0))
                    .put("observe_value", node -> safeGetDouble(node, "observe_value", 0.0))
                    .put("value_change", node -> safeGetDouble(node, "value_change", 0.0))
                    .put("value_change_ratio", node -> safeGetDouble(node, "value_change_ratio", 0.0))
                    .put("contribution", node -> safeGetDouble(node, "contribution", 0.0))
                    .put("contribution_ratio", node -> safeGetDouble(node, "contribution_ratio", 0.0))
                    .put("commonSummaryFromChildren", node -> "")
                    .build();

    public static void main(String[] args) throws Exception {
        ClassPathResource resource = new ClassPathResource("/static/tree.json");
        try (InputStream inputStream = resource.getInputStream()) {
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            byte[] data = new byte[8192];
            int nRead;
            while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            String jsonContent = new String(buffer.toByteArray(), StandardCharsets.UTF_8);
            JsonNode rootNode = mapper.readTree(jsonContent);
            JsonNode overallDataJson = rootNode.get("overall_data_json");
            String observeDate = safeGetText(overallDataJson, "observe_date", "");
            String baseDate = safeGetText(overallDataJson, "base_date", "");
            Double observeValue = safeGetDouble(overallDataJson, "observe_value", 0.0);
            Double baseValue = safeGetDouble(overallDataJson, "base_value", 0.0);
            String algorithmType = safeGetText(rootNode.get("input_data_json"), "algorithm_type", "");
            String direct = safeGetText(rootNode.get("input_data_json"), "direct", "");
            Double valueChange = safeGetDouble(overallDataJson, "overall_change", 0.0);
            Double valueChangeRatio = safeGetDouble(overallDataJson, "overall_change_ratio", 0.0);

            String valueChangeTrend = determineTrend(valueChange);

            JsonNode dimensionsData = rootNode.get("dimension_details_json");
            List<Map<String, Object>> resultArray = new ArrayList<>();
            JsonNode analysisDimensions = rootNode.get("analysis_dimensions_json");

            List<String> dimensionOrder = StreamSupport.stream(analysisDimensions.spliterator(), false)
                    .map(JsonNode::asText)
                    .collect(Collectors.toList());

            Map<String, String> dimensionLabels = new LinkedHashMap<>();
            for (int i = 0; i < dimensionOrder.size(); i++) {
                String dim = dimensionOrder.get(i);
                dimensionLabels.put(dim, dim + "_details"); // 用于匹配 key
            }

            for (int level = 0; level < dimensionOrder.size(); level++) {
                String dimName = dimensionOrder.get(level);
                String detailsKey = dimName + "_details";

                JsonNode detailsNode = dimensionsData.get(detailsKey);
                if (detailsNode == null || !detailsNode.isObject()) continue;

                String dimensionLabel = dimensionLabels.getOrDefault(dimName, dimName);
                List<Map<String, Object>> headDefinition = TESTUtils.createHeadDefinitionWithAlgorithmType(baseDate, observeDate, dimensionLabel, algorithmType);

                List<JsonNode> positiveNodes = TESTUtils.filterAndSortTopN(detailsNode, Constants.Limit.TOP_N, SortMode.POSITIVE, level, dimensionOrder);
                List<JsonNode> negativeNodes = TESTUtils.filterAndSortTopN(detailsNode, Constants.Limit.TOP_N, SortMode.NEGATIVE, level, dimensionOrder);
                List<JsonNode> combinedNodes = TESTUtils.filterAndSortTopN(detailsNode, Constants.Limit.TOP_N, SortMode.COMBINED, level, dimensionOrder);

                int finalLevel = level;
                List<Map<String, Object>> positiveRows = positiveNodes.stream()
                        .map(positiveNode -> collectNodeRecursive(positiveNode, finalLevel, dimensionOrder)).collect(Collectors.toList());

                List<Map<String, Object>> negativeRows = negativeNodes.stream()
                        .map(negativeNode -> collectNodeRecursive(negativeNode, finalLevel, dimensionOrder)).collect(Collectors.toList());

                List<Map<String, Object>> combinedRows = combinedNodes.stream()
                        .map(combinedNode -> collectNodeRecursive(combinedNode, finalLevel, dimensionOrder)).collect(Collectors.toList());

                List<Map<String, Object>> mergedRows = new ArrayList<>(positiveRows);
                mergedRows.addAll(negativeRows);

                Map<String, Object> dimensionResult = new LinkedHashMap<>();
                dimensionResult.put("dimensionKey", dimName);
                dimensionResult.put("head", headDefinition);
                dimensionResult.put("positiveRows", positiveRows);
                dimensionResult.put("negativeRows", negativeRows);
                dimensionResult.put("mergedRows", mergedRows);
                dimensionResult.put("combinedRows", combinedRows);
                resultArray.add(dimensionResult);
            }

            String commonSummary = generateLlmCommonFromResultArrayV3(direct, resultArray, dimensionLabels);

            Map<String, Object> finalResult = new LinkedHashMap<>();
            finalResult.put("observeDate", observeDate);
            finalResult.put("observeValue", observeValue);
            finalResult.put("baseDate", baseDate);
            finalResult.put("baseValue", baseValue);
            finalResult.put("valueChange", valueChange);
            finalResult.put("valueChangeRatio", valueChangeRatio);
            finalResult.put("valueChangeTrend", valueChangeTrend);
            finalResult.put("result", resultArray);
            finalResult.put("commonSummary", commonSummary);
            System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(finalResult));
        } catch (IOException e) {
            System.err.println("Error reading tree.json from resources: " + e.getMessage());
            e.printStackTrace();
        }
    }

    protected static class TESTUtils {
        protected static String generateLlmCommonFromResultArrayV3(String direct, List<Map<String, Object>> resultArray, Map<String, String> dimensionLabels) {
            if (resultArray == null || resultArray.isEmpty()) {
                return null;
            }
            List<Map<String, Object>> processedResultArray = resultArray.stream().map(dimResult -> {
                Map<String, Object> processedDim = new HashMap<>(dimResult);

                List<Map<String, Object>> positiveRows = (List<Map<String, Object>>) dimResult.get("positiveRows");
                if (positiveRows != null) {
                    List<Map<String, Object>> limitedPositive = positiveRows.stream()
                            .limit(Constants.Limit.POSITIVE_LIMIT)
                            .collect(Collectors.toList());
                    processedDim.put("positiveRows", limitedPositive);
                }

                List<Map<String, Object>> negativeRows = (List<Map<String, Object>>) dimResult.get("negativeRows");
                if (negativeRows != null) {
                    List<Map<String, Object>> limitedNegative = negativeRows.stream()
                            .limit(Constants.Limit.NEGATIVE_LIMIT)
                            .collect(Collectors.toList());
                    processedDim.put("negativeRows", limitedNegative);
                }

                return processedDim;
            }).collect(Collectors.toList());

            Map<String, Object> dataModel = new HashMap<>();
            dataModel.put("resultArray", processedResultArray);
            dataModel.put("dimensionLabels", dimensionLabels);
            dataModel.put("direct", direct);
            dataModel.put("Constants", new HashMap<>(ImmutableMap.of(
                    "POSITIVE_LIMIT", Constants.Limit.POSITIVE_LIMIT,
                    "NEGATIVE_LIMIT", Constants.Limit.NEGATIVE_LIMIT
            )));
            return VelocityRenderer.render("llm-common-result-template", dataModel);
        }

        protected static String generateChildrenSummaryV3(List<JsonNode> childrenNodes) {
            if (childrenNodes == null || childrenNodes.isEmpty()) {
                return "";
            }
            List<Map<String, Object>> positiveChildren = childrenNodes.stream()
                    .filter(child -> safeGetDouble(child, Constants.Sorting.SORT_KEY, 0.0) > 0)
                    .sorted((a, b) -> Double.compare(
                            safeGetDouble(b, Constants.Sorting.SORT_KEY, 0.0),
                            safeGetDouble(a, Constants.Sorting.SORT_KEY, 0.0)
                    ))
                    .limit(Constants.Limit.CHILD_POSITIVE_LIMIT)
                    .map(child -> {
                        Map<String, Object> m = new HashMap<>();
                        m.put("typeName", safeGetText(child, "typeName", safeGetText(child, "name", "")));
                        double contribRatio = safeGetDouble(child, "contribution_ratio", 0.0);
                        m.put("contributionPercent", Math.abs(contribRatio * 100));
                        return m;
                    })
                    .collect(Collectors.toList());

            List<Map<String, Object>> negativeChildren = childrenNodes.stream()
                    .filter(child -> safeGetDouble(child, Constants.Sorting.SORT_KEY, 0.0) < 0)
                    .sorted((a, b) -> Double.compare(
                            safeGetDouble(a, Constants.Sorting.SORT_KEY, 0.0),
                            safeGetDouble(b, Constants.Sorting.SORT_KEY, 0.0)
                    ))
                    .limit(Constants.Limit.CHILD_NEGATIVE_LIMIT)
                    .map(child -> {
                        Map<String, Object> m = new HashMap<>();
                        m.put("typeName", safeGetText(child, "typeName", safeGetText(child, "name", "")));
                        double contribRatio = safeGetDouble(child, "contribution_ratio", 0.0);
                        m.put("contributionPercent", Math.abs(contribRatio * 100));
                        return m;
                    })
                    .collect(Collectors.toList());

            Map<String, Object> dataModel = new HashMap<>();
            dataModel.put("positiveChildren", positiveChildren);
            dataModel.put("negativeChildren", negativeChildren);
            return VelocityRenderer.render("llm-common-children-summary-template", dataModel);
        }

        private static List<Map<String, Object>> createHeadDefinitionWithAlgorithmType(
                String baseDate, String observeDate, String dimensionLabel, String algorithmType) {

            List<Map<String, Object>> baseFields = ImmutableList.of(
                    ImmutableMap.of("code", "typeName", "name", dimensionLabel, "type", "String"),
                    ImmutableMap.of("code", "contribution", "name", "贡献度", "type", "number"),
                    ImmutableMap.of("code", "contribution_ratio", "name", "贡献值", "type", "number")
            );

            List<Map<String, Object>> additionalFields = Constants.Algorithm.ADDITION.equalsIgnoreCase(algorithmType)
                    ? ImmutableList.of()
                    : ImmutableList.of(
                    ImmutableMap.of("code", "within_contribution", "name", "自身表现贡献", "type", "number"),
                    ImmutableMap.of("code", "between_contribution", "name", "结构变化贡献", "type", "number")
            );

            List<Map<String, Object>> valueFields = ImmutableList.of(
                    ImmutableMap.of("code", "base_value", "name", baseDate, "type", "number"),
                    ImmutableMap.of("code", "observe_value", "name", observeDate, "type", "number"),
                    ImmutableMap.of("code", "compare_denominator", "name", "对比差值", "type", "number"),
                    ImmutableMap.of("code", "compare_ratio", "name", "差值百分比", "type", "number")
            );

            return Stream.of(baseFields, additionalFields, valueFields)
                    .flatMap(List::stream)
                    .collect(Collectors.toList());
        }

        protected static String determineTrend(Double value) {
            return Optional.ofNullable(value)
                    .map(v -> v > 0 ? Constants.Trend.UP : v < 0 ? Constants.Trend.DOWN : Constants.Trend.FLAT)
                    .orElse(Constants.Trend.FLAT);
        }

        protected static Map<String, Object> collectNodeRecursive(JsonNode node, int level, List<String> dimensionOrder) {
            if (node == null || !node.isObject()) {
                return Collections.emptyMap();
            }

            Map<String, Object> row = FIELD_EXTRACTORS.entrySet().stream()
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            entry -> entry.getValue().apply(node),
                            (a, b) -> a,
                            LinkedHashMap::new
                    ));

            String typeName = safeGetText(node, "typeName", "");
            if (typeName.isEmpty()) {
                typeName = safeGetText(node, "name", "");
            }
            row.put("typeName", typeName);

            List<Map<String, Object>> childrenList = new ArrayList<>();

            if (level + 1 < dimensionOrder.size()) {
                String nextDimName = dimensionOrder.get(level + 1);
                String containerKey = nextDimName + "_details";
                JsonNode container = node.get(containerKey);

                if (container != null && container.isObject()) {
                    List<Map.Entry<String, JsonNode>> entries = new ArrayList<>();
                    StreamSupport.stream(Spliterators.spliteratorUnknownSize(container.fields(), Spliterator.ORDERED), false)
                            .filter(entry -> entry.getValue().isObject())
                            .forEach(entries::add);

                    entries.sort((e1, e2) -> {
                        double v1 = safeGetDouble(e1.getValue(), Constants.Sorting.SORT_KEY, 0.0);
                        double v2 = safeGetDouble(e2.getValue(), Constants.Sorting.SORT_KEY, 0.0);
                        return Double.compare(Math.abs(v2), Math.abs(v1)); //
                    });

                    for (Map.Entry<String, JsonNode> entry : entries) {
                        JsonNode child = entry.getValue();
                        ObjectNode newNode = mapper.createObjectNode();
                        String childTypeName = entry.getKey();
                        newNode.put("typeName", childTypeName);

                        Set<String> skip = ImmutableSet.of("name", "typeName", "children");
                        StreamSupport.stream(Spliterators.spliteratorUnknownSize(child.fields(), Spliterator.ORDERED), false)
                                .filter(field -> !skip.contains(field.getKey()))
                                .forEach(field -> newNode.set(field.getKey(), field.getValue()));

                        childrenList.add(collectNodeRecursive(newNode, level + 1, dimensionOrder));
                    }
                }
            }

            String commonSummaryFromChildren = "";
            if (!childrenList.isEmpty()) {
                commonSummaryFromChildren = generateChildrenSummaryV3(
                        childrenList.stream()
                                .map(child -> {
                                    String tn = (String) child.get("typeName");
                                    Double contrib = (Double) child.get("contribution");
                                    Double contribRatio = (Double) child.get("contribution_ratio");
                                    ObjectNode mockNode = mapper.createObjectNode();
                                    mockNode.put("typeName", tn);
                                    mockNode.put("contribution", contrib);
                                    mockNode.put("contribution_ratio", contribRatio);
                                    return mockNode;
                                })
                                .collect(Collectors.toList())
                );
            }
            row.put("commonSummaryFromChildren", commonSummaryFromChildren);
            row.put("children", childrenList);
            return row;
        }
        private static Optional<Double> getDouble(JsonNode node, String key) {
            return Optional.ofNullable(node.get(key))
                    .filter(JsonNode::isNumber)
                    .map(JsonNode::asDouble);
        }

        enum SortMode {
            POSITIVE(n -> getDouble(n, Constants.Sorting.SORT_KEY).map(v -> v > 0).orElse(false)),
            NEGATIVE(n -> getDouble(n, Constants.Sorting.SORT_KEY).map(v -> v < 0).orElse(false)),
            COMBINED(n -> true);
            final Predicate<JsonNode> filter;

            SortMode(Predicate<JsonNode> filter) {
                this.filter = filter;
            }
        }

        protected static List<JsonNode> filterAndSortTopN(JsonNode node, int topN, SortMode mode, int level, List<String> dimensionOrder) {
            if (node == null || !node.isObject()) {
                return Collections.emptyList();
            }

            List<JsonNode> result = new ArrayList<>();

            StreamSupport.stream(Spliterators.spliteratorUnknownSize(node.fields(), Spliterator.ORDERED), false)
                    .filter(entry -> entry.getValue().isObject())
                    .forEach(entry -> {
                        JsonNode child = entry.getValue();
                        JsonNode contribNode = child.get(Constants.Sorting.SORT_KEY);
                        if (contribNode == null || !contribNode.isNumber()) return;

                        ObjectNode newNode = mapper.createObjectNode();
                        String typeName = Optional.ofNullable(child.get("name"))
                                .filter(n -> !n.isNull())
                                .map(JsonNode::asText)
                                .orElse(entry.getKey());
                        newNode.put("typeName", typeName);

                        Set<String> skip = ImmutableSet.of("name", "typeName", "children");
                        StreamSupport.stream(Spliterators.spliteratorUnknownSize(child.fields(), Spliterator.ORDERED), false)
                                .filter(field -> !skip.contains(field.getKey()))
                                .forEach(field -> newNode.set(field.getKey(), field.getValue()));

                        ArrayNode childArray = mapper.createArrayNode();
                        if (level + 1 < dimensionOrder.size()) {
                            String nextDimName = dimensionOrder.get(level + 1);
                            String containerKey = nextDimName + "_details";
                            JsonNode nextChild = child.get(containerKey);
                            if (nextChild != null && nextChild.isObject()) {
                                List<JsonNode> nested = filterAndSortTopN(nextChild, topN, mode, level + 1, dimensionOrder);
                                childArray.addAll(nested);
                            }
                        }
                        newNode.set("children", childArray);
                        result.add(newNode);
                    });

            return result.stream()
                    .filter(mode.filter)
                    .sorted((a, b) -> {
                        double aVal = getDouble(a, Constants.Sorting.SORT_KEY).orElse(0.0);
                        double bVal = getDouble(b, Constants.Sorting.SORT_KEY).orElse(0.0);
                        int compare;
                        switch (mode) {
                            case POSITIVE:
                            case COMBINED:
                                compare = Double.compare(bVal, aVal);
                                break;
                            case NEGATIVE:
                                compare = Double.compare(bVal, aVal);
//                                compare = Double.compare(Math.abs(bVal), Math.abs(aVal));
                                break;
                            default:
                                compare = 0;
                        }
                        return compare;
                    }).limit(topN)
                    .collect(Collectors.toList());
        }

        protected static String safeGetText(JsonNode node, String field, String defaultValue) {
            return Optional.ofNullable(node)
                    .map(n -> n.get(field))
                    .filter(f -> !f.isNull())
                    .map(JsonNode::asText)
                    .orElse(defaultValue);
        }

        protected static Double safeGetDouble(JsonNode node, String field, Double defaultValue) {
            return Optional.ofNullable(node)
                    .map(n -> n.get(field))
                    .filter(f -> !f.isNull() && f.isNumber())
                    .map(JsonNode::asDouble)
                    .orElse(defaultValue);
        }
    }
}
